#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/logging/log.h>

#include "serial.h"
#include "ble_types.h"
#include "pkt_codec.h"

LOG_MODULE_REGISTER(ble_central, LOG_LEVEL_INF);

#define BLE_ENC_BUF_SIZE 512
#define BLE_RSP_BUF_SIZE 64

static uint8_t ble_enc_buf[BLE_ENC_BUF_SIZE];
static uint8_t ble_rsp_buf[BLE_RSP_BUF_SIZE];

static struct bt_conn *default_conn;

extern struct serial_inst *serial_inst;

static void scan_recv(const struct bt_le_scan_recv_info *info,
                      struct net_buf_simple *buf);
static void scan_timeout(void);
static void conn_connected(struct bt_conn *conn, uint8_t err);
static void conn_disconnected(struct bt_conn *conn, uint8_t reason);
static bool conn_le_param_req(struct bt_conn *conn,
                              struct bt_le_conn_param *param);
static void conn_le_param_updated(struct bt_conn *conn, uint16_t interval,
                                  uint16_t latency, uint16_t timeout);
static void gatt_att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx);

struct bt_le_scan_cb scan_cb = {
    .recv = scan_recv,
    .timeout = scan_timeout,
};

struct bt_conn_cb conn_cb = {
    .connected = conn_connected,
    .disconnected = conn_disconnected,
    .le_param_req = conn_le_param_req,
    .le_param_updated = conn_le_param_updated,
#if defined(CONFIG_BT_USER_PHY_UPDATE)
    .le_phy_updated = conn_le_phy_updated,
#endif
#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE)
    .le_data_len_updated = conn_le_data_len_updated,
#endif
};

struct bt_gatt_cb gatt_cb = {
    .att_mtu_updated = gatt_att_mtu_updated,
};

static void scan_recv(const struct bt_le_scan_recv_info *info, struct net_buf_simple *buf)
{
    if (default_conn)
    {
        return;
    }

    char addr_str[BT_ADDR_LE_STR_LEN];
    bt_addr_le_to_str(info->addr, addr_str, sizeof(addr_str));
    printk("Device found: %s (RSSI %d)\n", addr_str, info->rssi);

    // pack scan result
    bt_le_evt_t ble_evt = {0};
    ble_evt.header.evt_id = BLE_EVT_SCAN_RESULT;
    ble_evt.header.evt_len = sizeof(bt_le_evt_t);

    bt_le_scan_evt_t *p_scan = (bt_le_scan_evt_t *)&ble_evt.evt.scan_evt;
    p_scan->params.result.peer_addr.type = info->addr->type;
    memcpy(p_scan->params.result.peer_addr.addr,
           info->addr->a.val, 6);
    p_scan->params.result.adv_type = info->adv_type;
    p_scan->params.result.sid = info->sid;
    p_scan->params.result.rssi = info->rssi;
    p_scan->params.result.tx_power = info->tx_power;
    p_scan->params.result.adv_props = info->adv_props;
    p_scan->params.result.interval = info->interval;
    p_scan->params.result.primary_phy = info->primary_phy;
    p_scan->params.result.secondary_phy = info->secondary_phy;
    p_scan->params.result.adv_data.len = buf->len;
    p_scan->params.result.adv_data.p_data = (uint8_t *)(uint32_t)(sizeof(bt_le_evt_t));

    // encoder
    codec_ctx_t codec_ctx = {0};
    pkt_encode_init(&codec_ctx, ble_enc_buf, sizeof(ble_enc_buf));
    pkt_encode(&codec_ctx, BLE_EVT_SCAN_RESULT, (uint8_t *)&ble_evt, sizeof(bt_le_evt_t), false);
    pkt_encode(&codec_ctx, BLE_EVT_SCAN_RESULT, buf->data, buf->len, true);

    if (serial_inst)
        serial_inst->tx(ble_enc_buf, codec_ctx.total_len);
}

static void scan_timeout(void)
{
    bt_le_evt_t ble_evt = {0};
    ble_evt.header.evt_id = BLE_EVT_TIMEOUT_SCAN;
    ble_evt.header.evt_len = sizeof(bt_le_evt_t);

    bt_le_evt_scan_timeout_t *p_to = (bt_le_evt_scan_timeout_t *)&ble_evt.evt.scan_evt;
    p_to->src = BLE_EVT_TIMEOUT_SCAN;

    // encoder
    codec_ctx_t codec_ctx = {0};
    pkt_encode_init(&codec_ctx, ble_enc_buf, sizeof(ble_enc_buf));
    pkt_encode(&codec_ctx, BLE_EVT_TIMEOUT_SCAN, (uint8_t *)&ble_evt, sizeof(bt_le_evt_t), true);

    if (serial_inst)
        serial_inst->tx(ble_enc_buf, codec_ctx.total_len);
}

static void conn_connected(struct bt_conn *conn, uint8_t err)
{
}

static void conn_disconnected(struct bt_conn *conn, uint8_t reason)
{
}

static bool conn_le_param_req(struct bt_conn *conn,
                              struct bt_le_conn_param *param)
{
    return true;
}

static void conn_le_param_updated(struct bt_conn *conn, uint16_t interval,
                                  uint16_t latency, uint16_t timeout)
{
}

static void gatt_att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
{
}

static void start_scan(void)
{
    int err;

    /* This demo doesn't require active scan */
    err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL);
    if (err)
    {
        printk("Scanning failed to start (err %d)\n", err);
        return;
    }

    printk("Scanning successfully started\n");
}

int ble_central_init(void)
{
    bt_le_scan_cb_register(&scan_cb);
    bt_conn_cb_register(&conn_cb);
    bt_gatt_cb_register(&gatt_cb);

    start_scan();

    return 0;
}
